Fix SIMD MinMax constant special cases - #133173
Conversation
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
|
Azure Pipelines: Successfully started running 5 pipeline(s). 11 pipeline(s) were filtered out due to trigger conditions. There may be pipelines that require an authorized user to comment /azp run to run. |
|
Tagging subscribers to this area: @JulieLeeMSFT, @jakobbotsch |
There was a problem hiding this comment.
Copilot review overview
🔵 Needs a closer look
It changes low-level JIT SIMD constant handling and codegen-path selection, so a final human review is needed to validate cross-architecture/codegen implications despite the added regression test.
Review tier: Lite
Findings: None
What changed in this PR
This PR updates CoreCLR JIT SIMD Min/Max handling so constant-lane “special values” (NaN and signed zero) are detected per-lane (any-lane) rather than requiring whole-vector uniformity, preventing incorrect selection of an uncompensated native min/max fast-path for mixed constants. It also adds a regression test covering NaN propagation and signed-zero behavior for Vector128/Vector256 Min/Max and MinNumber/MaxNumber scenarios.
Changes:
- Extend SIMD constant-evaluation helpers to support partial-vector sizes and add reusable mask predicates (
IsNaN,IsNegative,Is(±)Zero, any/all mask checks). - Add
GenTreeVecConpredicates for “contains any NaN / ±0 lane” and use them in the xarch Min/Max fast-path eligibility logic. - Add a new JIT regression test and wire it into the merged regression csproj.
| File | Description |
|---|---|
| src/coreclr/jit/simd.h | Adds SIMD mask helper predicates and any/all mask evaluation used to reason about special lanes in constants. |
| src/coreclr/jit/gentree.h | Declares new GenTreeVecCon “contains special lane” helpers used by Min/Max handling. |
| src/coreclr/jit/gentree.cpp | Implements the new predicates and updates Min/Max constant-path fixup gating to use any-lane NaN/±0 detection. |
| src/tests/JIT/Regression/JitBlue/Runtime_133022/Runtime_133022.cs | Adds coverage for NaN propagation and signed-zero semantics across Min/Max and MinNumber/MaxNumber, including constant-vs-opaque operand cases. |
| src/tests/JIT/Regression/Regression_ro_2.csproj | Includes the new regression test in the merged JIT regression project. |
|
| JIT | AVX512 | Runtime_133022 |
|---|---|---|
base 719009a |
on | 2 failures |
base 719009a |
off | 2 failures |
f3bc6de |
on | 2 failures |
f3bc6de |
off | 0 failures |
2. New wrong value under AVX512
Vector128<double>.MinNumber where the left operand is a constant containing a NaN and the right operand is opaque:
a = (0x7FF8000000000001 /* NaN */, 0xFFF0000000000000 /* -Inf */) constant
b = (0x7FF8000000000000 /* NaN */, 0xFFF8000000000000 /* -NaN */) opaque
| operand delivery | base 719009a |
f3bc6de |
|---|---|---|
| both constant | [7FF8000000000001, FFF0000000000000] |
[7FF8000000000001, FFF0000000000000] |
| left const, right opaque | [7FF8000000000001, FFF0000000000000] |
[7FF8000000000000, FFF8000000000000] |
| left opaque, right const | [7FF8000000000001, FFF0000000000000] |
[7FF8000000000001, FFF0000000000000] |
| both opaque (reference) | [7FF8000000000001, FFF0000000000000] |
[7FF8000000000001, FFF0000000000000] |
The folded result becomes the other operand wholesale. Lane 1 is the real problem and is not a NaN-payload nicety: MinNumber(-Inf, -NaN) must be -Inf, and it now yields -NaN.
Passes on 719009a at both gates and on f3bc6de with DOTNET_EnableAVX512=0; fails only on f3bc6de with AVX512 enabled. Needs a warmup loop so the method reaches tier 1.
standalone repro (exit 100 = pass, 101 = fail) — verified: PASS on base at both gates and on the PR with AVX512 off, FAIL only on the PR with AVX512 on
using System;
using System.Runtime.CompilerServices;
using System.Runtime.Intrinsics;
public static class Program
{
[MethodImpl(MethodImplOptions.NoInlining)]
private static double O(double v) => v; // opaque delivery
private static ulong Canon(double d) // canonicalize NaN payloads away
=> double.IsNaN(d) ? 0x7FF8000000000000UL : BitConverter.DoubleToUInt64Bits(d);
private static ulong H(Vector128<double> v)
{
ulong lo = Canon(v.GetElement(0)), hi = Canon(v.GetElement(1));
return lo ^ ((hi << 32) | (hi >> 32));
}
[MethodImpl(MethodImplOptions.NoInlining)]
private static bool Agree()
{
// NOTE: the constants must be written inline; routing them through a local
// or a helper defeats the fold and the test silently becomes all-opaque.
var bothConst = Vector128.MinNumber(
Vector128.Create(BitConverter.UInt64BitsToDouble(0x7FF8000000000001UL), BitConverter.UInt64BitsToDouble(0xFFF0000000000000UL)),
Vector128.Create(BitConverter.UInt64BitsToDouble(0x7FF8000000000000UL), BitConverter.UInt64BitsToDouble(0xFFF8000000000000UL)));
var rightOpaque = Vector128.MinNumber(
Vector128.Create(BitConverter.UInt64BitsToDouble(0x7FF8000000000001UL), BitConverter.UInt64BitsToDouble(0xFFF0000000000000UL)),
Vector128.Create(O(BitConverter.UInt64BitsToDouble(0x7FF8000000000000UL)), O(BitConverter.UInt64BitsToDouble(0xFFF8000000000000UL))));
var leftOpaque = Vector128.MinNumber(
Vector128.Create(O(BitConverter.UInt64BitsToDouble(0x7FF8000000000001UL)), O(BitConverter.UInt64BitsToDouble(0xFFF0000000000000UL))),
Vector128.Create(BitConverter.UInt64BitsToDouble(0x7FF8000000000000UL), BitConverter.UInt64BitsToDouble(0xFFF8000000000000UL)));
var reference = Vector128.MinNumber(
Vector128.Create(O(BitConverter.UInt64BitsToDouble(0x7FF8000000000001UL)), O(BitConverter.UInt64BitsToDouble(0xFFF0000000000000UL))),
Vector128.Create(O(BitConverter.UInt64BitsToDouble(0x7FF8000000000000UL)), O(BitConverter.UInt64BitsToDouble(0xFFF8000000000000UL))));
ulong h = H(reference);
return H(bothConst) == h && H(rightOpaque) == h && H(leftOpaque) == h;
}
public static int Main()
{
bool ok = true;
for (int i = 0; i < 200_000; i++) ok = Agree(); // warm to tier 1
Console.WriteLine(ok ? "PASS" : "FAIL: operand-delivery shapes disagree");
return ok ? 100 : 101;
}
}3. At scale
A differential fuzzer that emits the same computation in all four operand-delivery shapes and compares them, 300 seeds, identical seeds on both JITs:
| JIT | AVX512 on | AVX512 off |
|---|---|---|
base 719009a |
274 / 300 disagree | 222 / 300 disagree |
f3bc6de |
290 / 300 disagree | 0 / 300 |
Per seed, 21 go clean -> broken and 5 broken -> clean. So the non-AVX512 result is unambiguous (222 -> 0) and AVX512 is a net regression rather than merely unimproved.
Lead, not a conclusion: the failing shape is exactly "constant operand contains a NaN, other operand opaque", which is what the new GenTreeVecCon::ContainsNaN / needsFixup predicate newly enables. I have not read the resulting codegen, so I can't say whether the bug is in the predicate or in the AVX512 lowering it now reaches.
Scope confirmed only for Vector128<double>.MinNumber under fullopts; I have not validated 256/512 or MaxNumber.
Happy to run any experiment on this hardware if it helps.
Note
This comment was generated by GitHub Copilot.
|
My fuzzing indicates AVX-512 sensitivity here... can we also add some tests for it? |
…the result The single constant AVX-512 Fixup escape hatch was broken three ways: the table was broadcast, so a non-uniform constant had the zero sign forced in every element rather than only the ones that needed it; the isNumber operand swap overwrote the Fixup node's own operands; and the ZERO fixup token does not distinguish +0 from -0, so it cannot produce the sign-dependent answer that MinNumber/MaxNumber require. Build the table per element so mixed constants are handled, restrict the fixup to the Min/Max cases it can actually express, and let everything else fall through to the general handling. A partially NaN constant cannot use the operand ordering either, so it falls through as well. Also prefer the general IR over the AVX-512 Range/Fixup sequence when both inputs are constant, since Range/Fixup does not constant fold. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
|
Yeah, I added some comprehensive tests covering the various edges. I hadn't looked closely enough at the |
Add general Min/Max semantic coverage for both NaN signs and a focused mixed-lane regression for the single-constant optimization. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
There was a problem hiding this comment.
Copilot review overview
🟡 Changes recommended
The updated Min/Max special-casing logic is still unreachable for the “both operands are constant vectors” scenario central to #133022, so the regression may remain unfixed.
Once you've addressed the issues Copilot identified, you can request another Copilot review.
Review tier: Lite
Findings: 1
New issues introduced by this change (2)
| Severity | Finding |
|---|---|
src/coreclr/jit/gentree.cpp — This block’s logic is still only reached when exactly one operand is constant (`else if ((cnsNode… |
|
src/coreclr/jit/gentree.cpp — hasPartialNaN is documented as tracking partially NaN constants, but the current predicate is… |
|
The previous feedback was both false positives. |
|
/ba-g unrelated WASM failure |
|
/backport to release/11.0 |
|
/backport to release/10.0 |
|
Started backporting to |
|
Started backporting to |
|
@tannergooding backporting to git am output$ git cherry-pick a6bb62d7fcaa6ccdbb87551b48f537cd225fa1e0
Auto-merging src/coreclr/jit/gentree.cpp
Auto-merging src/coreclr/jit/gentree.h
Auto-merging src/tests/JIT/Regression/Regression_ro_2.csproj
CONFLICT (content): Merge conflict in src/tests/JIT/Regression/Regression_ro_2.csproj
error: could not apply a6bb62d7fca... Fix SIMD MinMax constant special cases (#133173)
hint: After resolving the conflicts, mark them with
hint: "git add/rm <pathspec>", then run
hint: "git cherry-pick --continue".
hint: You can instead skip this commit with "git cherry-pick --skip".
hint: To abort and get back to the state before "git cherry-pick",
hint: run "git cherry-pick --abort".
hint: Disable this message with "git config set advice.mergeConflict false"
$ git am --3way --empty=keep --ignore-whitespace --keep-non-patch changes.patch
Applying: Fix SIMD MinMax constant special cases
Using index info to reconstruct a base tree...
M src/coreclr/jit/gentree.cpp
M src/coreclr/jit/gentree.h
M src/tests/JIT/Regression/Regression_ro_2.csproj
Falling back to patching base and 3-way merge...
Auto-merging src/coreclr/jit/gentree.cpp
Auto-merging src/coreclr/jit/gentree.h
Auto-merging src/tests/JIT/Regression/Regression_ro_2.csproj
CONFLICT (content): Merge conflict in src/tests/JIT/Regression/Regression_ro_2.csproj
error: Failed to merge in the changes.
hint: Use 'git am --show-current-patch=diff' to see the failed patch
hint: When you have resolved this problem, run "git am --continue".
hint: If you prefer to skip this patch, run "git am --skip" instead.
hint: To restore the original branch and stop patching, run "git am --abort".
hint: Disable this message with "git config set advice.mergeConflict false"
Patch failed at 0001 Fix SIMD MinMax constant special cases
Error: The process '/usr/bin/git' failed with exit code 128 |
|
@tannergooding backporting to git am output$ git cherry-pick a6bb62d7fcaa6ccdbb87551b48f537cd225fa1e0
Auto-merging src/coreclr/jit/gentree.cpp
CONFLICT (content): Merge conflict in src/coreclr/jit/gentree.cpp
Auto-merging src/coreclr/jit/gentree.h
Auto-merging src/coreclr/jit/simd.h
Auto-merging src/libraries/Common/tests/System/GenericMathTestMemberData.cs
CONFLICT (modify/delete): src/tests/JIT/Regression/Regression_ro_2.csproj deleted in HEAD and modified in a6bb62d7fca (Fix SIMD MinMax constant special cases (#133173)). Version a6bb62d7fca (Fix SIMD MinMax constant special cases (#133173)) of src/tests/JIT/Regression/Regression_ro_2.csproj left in tree.
error: could not apply a6bb62d7fca... Fix SIMD MinMax constant special cases (#133173)
hint: After resolving the conflicts, mark them with
hint: "git add/rm <pathspec>", then run
hint: "git cherry-pick --continue".
hint: You can instead skip this commit with "git cherry-pick --skip".
hint: To abort and get back to the state before "git cherry-pick",
hint: run "git cherry-pick --abort".
hint: Disable this message with "git config set advice.mergeConflict false"
$ git am --3way --empty=keep --ignore-whitespace --keep-non-patch changes.patch
Applying: Fix SIMD MinMax constant special cases
Using index info to reconstruct a base tree...
M src/coreclr/jit/gentree.cpp
M src/coreclr/jit/gentree.h
M src/coreclr/jit/simd.h
A src/tests/JIT/Regression/Regression_ro_2.csproj
Falling back to patching base and 3-way merge...
Auto-merging src/coreclr/jit/gentree.cpp
CONFLICT (content): Merge conflict in src/coreclr/jit/gentree.cpp
Auto-merging src/coreclr/jit/gentree.h
Auto-merging src/coreclr/jit/simd.h
CONFLICT (modify/delete): src/tests/JIT/Regression/Regression_ro_2.csproj deleted in HEAD and modified in Fix SIMD MinMax constant special cases. Version Fix SIMD MinMax constant special cases of src/tests/JIT/Regression/Regression_ro_2.csproj left in tree.
error: Failed to merge in the changes.
hint: Use 'git am --show-current-patch=diff' to see the failed patch
hint: When you have resolved this problem, run "git am --continue".
hint: If you prefer to skip this patch, run "git am --skip" instead.
hint: To restore the original branch and stop patching, run "git am --abort".
hint: Disable this message with "git config set advice.mergeConflict false"
Patch failed at 0001 Fix SIMD MinMax constant special cases
Error: The process '/usr/bin/git' failed with exit code 128 |


SIMD Min/Max constant handling used whole-vector checks for NaN and signed zero when deciding whether native xarch Min/Max needed compensation. Constants containing a mix of ordinary and special lanes could therefore lose the managed NaN or signed-zero semantics.
This evaluates the predicates through reusable SIMD mask helpers matching
IsNaN/IsNegativeandAnyWhereAllBitsSet/AllWhereAllBitsSet, then uses any-lane checks for fast-path eligibility.Fixes #133022
Note
This pull request was created with GitHub Copilot.